# coding: utf-8

import math
from typing import Sequence, Optional
from graph import (
    UVEdge,
    vertex_t,
    NodeAttr,
    EdgeAttr,
    GraphAttr,
    vertex_map,
    Subgraph,
    Hierarchy,
)
from cycle import RevEdges
from .EdgeRouter import EdgeRouter
from vec2 import vec2, to_degrees, dot, distance, to_radians
from utils import sgn


class SimpleEdgeRouter(EdgeRouter):

    loop_height: float
    min_shift: float

    attr: GraphAttr
    nodes: vertex_map[NodeAttr]  # node -> NodeAttr  path -> EdgeAttr
    links: Sequence[EdgeAttr]

    # vertex_map< std::array< float, 4 > > angles;
    # vertex_map< std::array< float, 4 > > bound;
    # vertex_map< std::array< float, 4> > shifts;
    def __init__(
        self,
        attr: GraphAttr,
        nodes: vertex_map[NodeAttr] = None,
        paths: Sequence[EdgeAttr] = None,
    ):
        self.min_sep: float = 5
        self.loop_angle_sep: float = 5
        self.attr = attr
        self.nodes = nodes
        self.links = paths

    def run(self, h: Hierarchy, rev: RevEdges):

        g = h.graph()
        self.angles = vertex_map(g, "angles", lambda: [90.0, 90.0, 90.0, 90.0])
        self.bound = vertex_map(g, "bound", lambda: [0.0, 0.0, 0.0, 0.0])
        self.shifts = vertex_map(g, "shifts", lambda: [0.0, 0.0, 0.0, 0.0])
        self.loop = vertex_map(g, "loop", False)
        # min_shift = self.attr.node_size/2
        # loop_height = self.attr.node_size/2

        for u in rev.loops:
            self.loop.set(u, True)
            self.make_loop_square(g, u)
        #  set_dummy_shifts(h);
        for l_layers in h.layers:
            # TODO
            for d in [1, -1]:
                for u in l_layers:
                    for v in g.out_neighbours(u):
                        if not g.is_dummy_node(u) or not g.is_dummy_node(v):
                            self.set_regular_shifts2(h, UVEdge(u, v))

        """for (auto u : h.g.get_all_nodes()()):
            for (auto v : h.g.out_neighbours(u)):
                if (!h.g.is_dummy_node(u) || !h.g.is_dummy_node(v)) set_regular_shifts2(h,:u, v});
            }
        """
        self.unify_dummy_shifts(h)

        for u in g.get_all_nodes():
            for v in g.out_neighbours(u):
                if not g.is_dummy_node(u):
                    self.make_path(h, rev, u, v)

    def set_regular_shifts2(self, h: Hierarchy, e: UVEdge):
        dirs = self.get_directions_UVEdge(e)
        if dirs.x == 0:
            return
        self.set_reg_shift2(h, e, dirs)

    def set_reg_shift2(self, h: Hierarchy, e: UVEdge, dirs: vec2):
        self.check_loop(e)
        # print('=========', e)
        # print('-----------', reversed(e))
        self.check_loop(e.reversed())

        up = self.next_non_dummy(h, e.u, dirs.x)
        down = self.next_non_dummy(h, e.v, -dirs.x)

        up_d = self.next_vertex(h, e.u, dirs.x)
        down_d = self.next_vertex(h, e.v, -dirs.x)

        self.set_shift2(h, e, up, down, dirs)
        self.set_shift2(h, e, up, down_d, dirs)
        # print('-===================', down)
        self.set_shift2(h, e, up_d, down, dirs)
        self.set_shift2(h, e, up_d, down_d, dirs)

    def set_shift2(
        self,
        h: Hierarchy,
        e: UVEdge,
        up: Optional[vertex_t],
        down: Optional[vertex_t],
        dirs: vec2,
    ):
        g = h.graph()
        v_from = self.get_center(e.u, dirs)
        v_to = self.get_center(e.v, -dirs)

        c_up: vec2 = self.pos(e.u) - dirs.x * 10
        r_up: float = 0.0
        if up is not None:
            c_up = self.pos(up)
            r_up = (
                self.shifts[up][self.angle_idx(dirs)]
                if g.is_dummy_node(up)
                else self.attr.node_size
            )
            # r_up = self.shifts[up][self.angle_idx(dirs)] if g.is_dummy_node(up) else self.attr.node_size
        c_down: vec2 = self.pos(e.u) - dirs.x * 10
        r_down: float = 0.0
        if down is not None:
            c_down = self.pos(down)

            r_down = (
                self.shifts[down][self.angle_idx(-dirs)]
                if g.is_dummy_node(down)
                else self.attr.node_size
            )
        can_inter_up = sgn(c_up.x - v_from.x) != sgn(c_up.x - v_to.x)
        can_inter_down = sgn(c_down.x - v_from.x) != sgn(c_down.x - v_to.x)
        node_size = self.attr.node_size

        # if it is possible for the UVEdge to intersect the vertex
        if can_inter_up or can_inter_down:
            s: float = self.get_shift_by_vertex(e.u, dirs)
            t: float = self.get_shift_by_vertex(e.v, -dirs)
            up_done: bool = True
            down_done: bool = True
            while True:
                if (
                    can_inter_up
                    and s <= node_size
                    and self.line_point_dist(v_from, v_to, c_up) <= r_up + self.min_sep
                ):
                    s += 5
                    v_from = self.pos(e.u) + vec2(0, dirs.y * s)
                    up_done = False
                else:
                    up_done = True

                if (
                    can_inter_down
                    and t <= node_size
                    and self.line_point_dist(v_to, v_from, c_down)
                    <= r_down + self.min_sep
                ):
                    t += 5
                    v_to = self.pos(e.v) + vec2(0, -dirs.y * t)
                    down_done = False
                else:
                    down_done = True

                if up_done and down_done:
                    break

            # clip it
            if s > node_size:
                s = node_size
            if t > node_size:
                t = node_size

            # shifts_u_value = max(g.get_node_array_attr(
            #     e.u, 'shifts', self.angle_idx(dirs)), s)
            # g.set_node_array_attr(
            #     e.u, 'shifts', self.angle_idx(dirs), shifts_u_value)
            # shifts_v_value = max(g.get_node_array_attr(
            #     e.v, 'shifts', self.angle_idx(-dirs)), t)
            # g.set_node_array_attr(
            #     e.v, 'shifts', self.angle_idx(-dirs), shifts_v_value)
            self.shifts[e.u][self.angle_idx(dirs)] = max(
                self.shifts[e.u][self.angle_idx(dirs)], s
            )
            self.shifts[e.v][self.angle_idx(-dirs)] = max(
                self.shifts[e.v][self.angle_idx(-dirs)], t
            )

    def get_shift_by_edge(self, e: UVEdge) -> float:
        return self.get_shift_by_vertex(e.u, self.get_directions_UVEdge(e))

    def get_shift_by_vertex(self, u: vertex_t, dirs: vec2) -> float:
        return self.get_shift_by_quadrant(u, self.angle_idx(dirs))

    def get_shift_by_quadrant(self, u: vertex_t, quadrant: int) -> float:
        return self.shifts[u][quadrant]
        # return self.shifts[u][quadrant]

    def unify_dummy_shifts(self, h: Hierarchy):
        g = h.graph()
        for layer_idx in range(h.size()):
            l_layer_idx = h.layers[layer_idx]
            j = -1
            for i in range(len(l_layer_idx)):
                if not g.is_dummy_node(l_layer_idx[i]):
                    self.calculate_block_shifts(h, layer_idx, j + 1, i - 1)
                    j = i
            self.calculate_block_shifts(h, layer_idx, j + 1, len(l_layer_idx) - 1)

    def calculate_block_shifts(self, h: Hierarchy, layer: int, start: int, end: int):
        g = h.graph()
        if end < start:
            return
        l_layer = h.layers[layer]
        s = [0.0, 0.0, 0.0, 0.0]

        for i in range(start, end + 1):
            u: vertex_t = l_layer[i]
            assert g.is_dummy_node(u)
            s[0] = max(s[0], self.shifts[u][0])
            s[1] = max(s[1], self.shifts[u][1])
            s[2] = max(s[2], self.shifts[u][2])
            s[3] = max(s[3], self.shifts[u][3])
        for i in range(start, end + 1):
            # g.set_node_array_attr(l_layer[i], 'shifts', 0, s[0])
            # g.set_node_array_attr(l_layer[i], 'shifts', 0, s[1])
            # g.set_node_array_attr(l_layer[i], 'shifts', 0, s[2])
            # g.set_node_array_attr(l_layer[i], 'shifts', 0, s[3])
            self.shifts[l_layer[i]][0] = s[0]
            self.shifts[l_layer[i]][1] = s[1]
            self.shifts[l_layer[i]][2] = s[2]
            self.shifts[l_layer[i]][3] = s[3]
        self.fix_reg_ends(h, layer, start, end)

    def fix_reg_ends(self, h: Hierarchy, layer: int, start: int, end: int):
        g = h.graph()
        l_layer = h.layers[layer]
        if h.has_prev(l_layer[start]):
            u = h.prev(l_layer[start])
            for v in g.neighbours(u):
                dirs = self.get_directions_UVEdge(UVEdge(u, v))
                if dirs.x != 0:
                    self.set_reg_shift2(h, UVEdge(u, v), dirs)
        if h.has_next(l_layer[end]):
            u = h.next(l_layer[end])
            for v in g.neighbours(u):
                dirs = self.get_directions_UVEdge(UVEdge(u, v))
                if dirs.x != 0:
                    self.set_reg_shift2(h, UVEdge(u, v), dirs)

    def check_loop(self, e: UVEdge):
        dirs = self.get_directions_UVEdge(e)  # loop是属性
        if (
            not self.loop.at(e.u)
            or self.angle_idx(dirs) == 2
            or self.angle_idx(dirs) == 3
        ):
            return

        s: float = self.get_shift_by_vertex(e.u, dirs)
        v_from = self.get_center(e.u, dirs)
        v_to = self.get_center(e.v, -dirs)
        intersection = self.edge_intersects(
            v_from, v_to, self.nodes[e.u].pos, self.nodes[e.u].size
        )
        a: float = self.angle(self.pos(e.u), intersection)

        if a > self.attr.loop_angle - self.loop_angle_sep:
            a = self.attr.loop_angle - self.loop_angle_sep
            p = self.angle_point(a, e.u, dirs)

            t: float = (self.pos(e.u).x - p.x) / (p.x - v_to.x)
            s = abs(self.pos(e.u).y - (p.y + t * (p.y - v_to.y)))
            assert s >= 0 and s <= self.nodes[e.u].size
            self.shifts[e.u][self.angle_idx(dirs)] = s

    def get_center(self, u: vertex_t, dirs: vec2) -> vec2:
        return self.nodes[u].pos + vec2(0, dirs.y * self.get_shift_by_vertex(u, dirs))

    def centered_angle(self, e: UVEdge) -> float:
        dir_ = self.pos(e.v) - self.pos(e.u)
        return to_degrees(math.atan(abs(dir_.x) / abs(dir_.y)))

    def angle(self, v_from: vec2, v_to: vec2) -> float:
        dir_ = v_to - v_from
        return to_degrees(math.atan(abs(dir_.x) / abs(dir_.y)))

    def shift_idx(dirs: vec2) -> int:
        return dirs.y == 1

    """
     * Get the index of the quadrant <dirs> points to.
     * The indexes are as follows:
     *    2 | 1
     *   ---+--->
     *    3 | 0
     *      v
    """

    def angle_idx(self, dirs: vec2) -> int:
        if dirs.x == 1:
            return dirs.y != 1
        return 2 + (dirs.y == 1)

    def pos(self, u: vertex_t) -> vec2:
        return self.nodes[u].pos

    def get_directions_vec2(self, v: vec2) -> vec2:
        return vec2(sgn(v.x), sgn(v.y))

    def get_directions_UVEdge(self, e: UVEdge) -> vec2:
        return self.get_directions_vec2(self.pos(e.v) - self.pos(e.u))  # 重名

    def get_xdir(self, e: UVEdge) -> int:
        return sgn(self.pos(e.v).x - self.pos(e.u).x)

    # finds next vertex in the desired direction which is not a dummy vertex
    def next_non_dummy(self, h: Hierarchy, u: vertex_t, d: int) -> Optional[vertex_t]:
        g = h.graph()
        i: int = h.get_pos(u) + d
        while h.valid_pos(h.get_rank(u), i) and g.is_dummy_node(h.get_layer(u)[i]):
            i += d
        if h.valid_pos(h.get_rank(u), i):
            return h.get_layer(u)[i]
        return None

    def next_vertex(self, h: Hierarchy, u: vertex_t, d: int) -> Optional[vertex_t]:
        if d == -1 and h.has_prev(u):
            return h.prev(u)
        if d == 1 and h.has_next(u):
            return h.next(u)

        return None

    def make_path(self, h: Hierarchy, rev: RevEdges, u: vertex_t, v: vertex_t):
        g = h.graph()
        path: EdgeAttr = EdgeAttr(u, v)
        orig = UVEdge(u, v)
        ps = self.calculate_port_shifted(u, self.nodes[v].pos - self.nodes[u].pos)
        if ps is not None:
            path.points.append(ps)

        while g.is_dummy_node(v):
            s = self.shifts[v][self.angle_idx(self.get_directions_UVEdge(UVEdge(v, u)))]
            if s > 0:
                path.points.append(self.nodes[v].pos + vec2(0, -s))
            path.points.append(self.nodes[v].pos)
            n = self.next(g, v)
            s = self.shifts[v][self.angle_idx(self.get_directions_UVEdge(UVEdge(v, n)))]
            if s > 0:
                path.points.append(self.nodes[v].pos + vec2(0, s))
            u = v
            v = n

        ps = self.calculate_port_shifted(v, self.nodes[u].pos - self.nodes[v].pos)
        if ps is not None:
            path.points.append(ps)
        path.v = v
        # print('分支', u, v, self.nodes[u].pos, self.nodes[v].pos, "make path:", path.points,self.calculate_port_shifted(
        #     v, self.nodes[u].pos - self.nodes[v].pos))

        # TODO
        if rev.reversed.contains(orig.u, orig.v):
            self.reverse(path)
        elif rev.collapsed.contains(orig.u, orig.v):
            path.bidirectional = True
        # TODO
        self.links.append(path)

    def reverse(self, path: EdgeAttr):
        path.u, path.v = path.v, path.u
        for i in range(len(path.points) // 2):
            path.points[i], path.points[len(path.points) - i - 1] = (
                path.points[len(path.points) - i - 1],
                path.points[i],
            )

    # https://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm
    def edge_intersects(
        self, v_from: vec2, v_to: vec2, center: vec2, r: float
    ) -> Optional[vec2]:
        d = v_to - v_from
        f = v_from - center

        a = dot(d, d)
        b = 2 * dot(f, d)
        c = dot(f, f) - r * r
        discriminant: float = b * b - 4 * a * c
        # print(discriminant)
        if discriminant < 0:
            return None
        discriminant = math.sqrt(discriminant)
        # TODO
        # if 2*a == 0:
        #     return None
        # else:
        t1: float = (-b - discriminant) / (2 * a)
        t2: float = (-b + discriminant) / (2 * a)
        # print(t1, t2)

        if t1 >= 0 and t1 <= 1:
            return v_from + t1 * d

        if t2 >= 0 and t2 <= 1:
            return v_from + t2 * d
        return None

    # http://geomalgorithms.com/a02-_lines.html

    def line_point_dist(self, v_from: vec2, v_to: vec2, p: vec2) -> float:
        v = v_to - v_from
        w = p - v_from
        c1: float = dot(w, v)
        c2: float = dot(v, v)
        t: float = c1 / c2

        x = v_from + t * v
        return distance(p, x)

    def make_loop_square(self, g: Subgraph, u: vertex_t):
        path = EdgeAttr(u, u)
        # TODO
        path.points = [vec2()] * 4
        path.points[0] = self.angle_point(self.attr.loop_angle, u, vec2(1, -1))
        path.points[3] = self.angle_point(self.attr.loop_angle, u, vec2(1, 1))

        path.points[1] = vec2(
            self.pos(u).x + self.attr.node_size + self.attr.loop_size / 2,
            path.points[0].y,
        )
        path.points[2] = vec2(
            self.pos(u).x + self.attr.node_size + self.attr.loop_size / 2,
            path.points[3].y,
        )
        self.links.append(path)

    def angle_point(self, angle: float, u: vertex_t, dirs: vec2) -> vec2:
        x = dirs.x * self.attr.node_size * math.sin(to_radians(angle))
        y = dirs.y * self.attr.node_size * math.cos(to_radians(angle))
        return self.pos(u) + vec2(x, y)

    # TODO 找出哪里用到了 next

    def next(self, g: Subgraph, u: vertex_t) -> vertex_t:
        return next(iter(g.out_neighbours(u)))

    def prev(self, g: Subgraph, u: vertex_t) -> vertex_t:
        return next(iter(g.in_neighbours(u)))

    def calculate_port_shifted(self, u: vertex_t, dir: vec2) -> vec2:
        dirs = vec2(sgn(dir.x), sgn(dir.y))
        s = self.get_shift_by_vertex(u, dirs)
        if s == self.attr.node_size:
            return self.get_center(u, dirs)
        center = self.get_center(u, dirs)
        # if u == 1:
        # print("yyy:", dir, dirs, s, self.attr.node_size, self.pos(u), center)

        ret = self.edge_intersects(
            center, center + dir, self.pos(u), self.nodes[u].size
        )
        # HACK 测试用
        # if u == 1:
        #     print('xxxx:', u, center,center + dir, self.pos(u), ret)
        return ret

    def calculate_port_centered(self, u: vertex_t, dir: vec2) -> vec2:
        return self.nodes[u].pos + self.nodes[u].size * dir.normalized()

    def calculate_port_single(self, u: vertex_t, dir: vec2) -> vec2:
        return self.nodes[u].pos + vec2(0, sgn(dir.y) * self.nodes[u].size)

    def calculate_port_arctan(self, u: vertex_t, dir: vec2) -> vec2:
        angle: float = (1 / 2) * math.atan(dir.x / 256)
        x: float = self.nodes[u].size * math.sin(angle)
        y: float = self.nodes[u].size * math.cos(angle)
        return self.nodes[u].pos + vec2(x, sgn(dir.y) * y)
